using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Reflection;
using System.IO;

using ca.metaobjects.csogateway.Reflection;

namespace ca.metaobjects.csogateway
{
	/// <summary>
	/// <para>
	/// Handles HTTP request made to the CSO Gateway to query the in-memory collection of cso-types.
	/// </para>
	/// <para>
	/// The output of an HTTP request to this handler depends on the query string. This handler accepts only one key/value pair in the query string. The key is compared
	/// against a list of command names and the value is used to pass parameters to the command.
	/// </para>
	/// 
	/// <table>
	/// <tr>
	///		<th>COMMAND</th><th>FORMAT OF PARAMETERS</th><th>CONTENT TYPE</th><th>COMMENT</th>
	/// </tr>
	/// <tr>
	///		<td>import</td><td>((#assembly-qualified name of type#)(#full name of cso-type#))</td><td>text/javascript</td><td>Create cso-type is not created already. Return js code the cso-type.</td>
	/// </tr>
	/// <tr>
	///		<td>view</td><td>#full name of cso-type#</td><td>text/html</td><td>For debugging. Return js code of an existing cso-type.</td>
	/// </tr>
	/// <tr>
	///		<td>reset</td><td>all</td><td>text/html</td><td>Flushes all in-memory cso-types. Not thread-safe. For debugging only.</td>
	/// </tr>
	/// </table>
	/// 
	/// <para>
	/// A request with the 'import' command will return a 'text/javascript' reponse containing the javascript code defining a cso-type. First, the handler will try
	/// to locate the cso-type with the given cso-type full name. If it is not found, it may be because it is a cso-generated type that has not been created and loaded in memory yet.
	/// Cso-generated types are created from types so an actual assembly-qualified name of a type is needed to created a cso-type. The given assembly-qualified name
	/// is passed to the System.Type.GetType(string) method. If a type is return and if the type is cso-ready, the cso-type is created by the Type Morpher
	/// and the javascript code defining it is written to the response.
	/// </para>
	/// <para>
	/// A request with the 'view' command will return a text/html reponse containing the javascript code defining a cso-type. The cso-type must already be loaded in
	/// memory or an error message is returned. This is a debugging functionnaly that allows developper to see the source code of cso-type easily.
	/// </para>
	/// <para>
	/// A request with the 'import' command and 'all' in as parameter will flush all loaded cso-type from the memory. This functionnality is not thread-safe and should only be used
	/// for debugging.
	/// </para>
	/// </summary>
    public class ImporterHttpHandler : IHttpHandler
	{
		private const string importTypeCmdName = "import";
		private const string viewCsoTypeForCmdName = "view";
		private const string resetCmdName = "reset";

		private string[] commandList = new string[] {importTypeCmdName, viewCsoTypeForCmdName, resetCmdName};

		/// <summary>
		/// Creates a new instance of ImporterHttpHandler
		/// </summary>
		public ImporterHttpHandler() {}
        
        #region IHttpHandler Members

		/// <summary>
		/// ImporterHttpHandler is reusable HTTP handler. This method returns true.
		/// </summary>
        public bool IsReusable
        {
            get { return true; }
        }

		/// <summary>
		/// Based on the command specified in the query string and on its parameters, produce a response to the request and set the appropriate HTTP headers.
		/// </summary>
		/// <param name="context">The context of the current HTTP request</param>
        public void ProcessRequest(HttpContext context)
        {
			if (context.Request.QueryString.Count != 1)
			{
				throw new CsoGatewayException(ExceptionMessage.I_000);
			}
			else if (context.Request.QueryString[importTypeCmdName] != null)
			{
				string scriptDescriptor = context.Request.QueryString[importTypeCmdName];
				string sourceCode;

				if (scriptDescriptor.EndsWith(".js"))
				{
					sourceCode = Loader.getScriptForFileName(scriptDescriptor);
				}
				else
				{
					string csoGenTypeName = DependencyGraph.CsoGeneratedTypeNameInDescriptor(scriptDescriptor);
					string assemblyQualifiedName = DependencyGraph.AssemblyQualifiedNameInCsoGeneraedTypeDescriptor(scriptDescriptor);

					if (!Loader.csoTypeExist(csoGenTypeName)) // Maybe CsoType has not been loaded yet.
					{
						Type aType = Type.GetType(assemblyQualifiedName, true, false);
						Loader.createCsoGeneratedType(aType);
					}

					sourceCode = Loader.getScriptForTypeName(csoGenTypeName);
				}

				context.Response.ClearHeaders();
				context.Response.Cache.SetExpires(DateTime.Now.AddMinutes(60));
				context.Response.CacheControl = "private";
				context.Response.ContentType = "text/javascript";
				context.Response.Write(sourceCode);
				context.Response.End();
			}
			else if (context.Request.QueryString[viewCsoTypeForCmdName] != null)
			{
				string responseMessage = String.Empty;
				string typeSourceCode = String.Empty;

				string typeName = context.Request.QueryString[viewCsoTypeForCmdName];

				Type aType;

				aType = Type.GetType(typeName, false, false);

				if (aType == null)
				{
					Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
					for (int i = 0; i < assemblies.Length; ++i)
					{
						aType = assemblies[i].GetType(typeName, false, false);
						if (aType != null)
							break;

					}
				}

				if (aType != null)
				{
					if (Loader.javascriptPrimitiveTypeExistFor(aType) || Loader.javascriptPredefinedCoreObjectExistFor(aType))
					{
						responseMessage = String.Format("Type '{0}' maps to a javascript primitive type or a javascript predefined core object", aType.FullName);
					}
					else
					{
						if (Loader.isNullableOfCsoReadyType(aType))
						{
							aType = aType.GetGenericArguments()[0];
						}

						if (Loader.isCsoReady(aType))
						{
							Loader.createCsoGeneratedType(aType);
						}

						if (Loader.csoTypeExistFor(aType))
						{
							responseMessage = String.Format("Server side: '{1}'<br />Client side: '{0}'", Loader.getCsoTypeForType(aType).FullTypeName, aType.AssemblyQualifiedName);

							if (Loader.csoGeneratedTypeExistFor(aType))
							{
								typeSourceCode = ((CsoGeneratedType)Loader.getCsoTypeForType(aType)).SourceCode;
							}
							else // is CsoNativeType
							{
								CsoNativeType natType = (CsoNativeType)Loader.getCsoTypeForType(aType);
								typeSourceCode = String.Format("<a href=\"csogateway.axd?import={0}\">View Source Code</a>", natType.ScriptFileInfo.FileName); ;
							}
						}
						else
						{
							responseMessage = String.Format("Type '{0}' is not cso-compatible.");
						}
					}
				}
				else
				{
					responseMessage = String.Format("Cannot find type '{0}'.", typeName);
				}

				context.Response.ClearHeaders();
				context.Response.CacheControl = "private";
				context.Response.ContentType = "text/html";
				context.Response.Write(String.Format("<html><body><h3>{0}</h3><br /><br /><pre>{1}</pre></body></html>", responseMessage, typeSourceCode));
				context.Response.End();
			}
			else if (context.Request.QueryString[resetCmdName] != null)
			{
				string param = context.Request.QueryString[resetCmdName];
				string responseMessage;

				if (param == "all")
				{
					Loader.reset();
					responseMessage = "All CsoTypes were flushed.";
				}
				else
				{
					responseMessage = String.Format("Unrecognized sub-command: {0}", param);
				}

				context.Response.ClearHeaders();
				context.Response.CacheControl = "private";
				context.Response.ContentType = "text/html";
				context.Response.Write(String.Format("<html><body><h3>{0}</h3><br /><br /></body></html>", responseMessage));
				context.Response.End();
			}
			else
			{
				throw new CsoGatewayException(ExceptionMessage.I_001, String.Join(",", commandList));
			}
        }

        #endregion
    }
}
